#include "FBXLoader.hpp"
#include <cassert>

static int numTabs = 0;

static void printTabs()
{
	for(int i = 0; i < numTabs; i++)
		printf("\t");
}

// Return a string-based representation based on the attribute type.
static FbxString getAttributeTypeName(FbxNodeAttribute::EType type)
{
	switch(type)
	{
		case FbxNodeAttribute::eUnknown: return "unidentified"; 
        case FbxNodeAttribute::eNull: return "null"; 
        case FbxNodeAttribute::eMarker: return "marker"; 
        case FbxNodeAttribute::eSkeleton: return "skeleton"; 
        case FbxNodeAttribute::eMesh: return "mesh"; 
        case FbxNodeAttribute::eNurbs: return "nurbs"; 
        case FbxNodeAttribute::ePatch: return "patch"; 
        case FbxNodeAttribute::eCamera: return "camera"; 
        case FbxNodeAttribute::eCameraStereo: return "stereo"; 
        case FbxNodeAttribute::eCameraSwitcher: return "camera switcher"; 
        case FbxNodeAttribute::eLight: return "light"; 
        case FbxNodeAttribute::eOpticalReference: return "optical reference"; 
        case FbxNodeAttribute::eOpticalMarker: return "marker"; 
        case FbxNodeAttribute::eNurbsCurve: return "nurbs curve"; 
        case FbxNodeAttribute::eTrimNurbsSurface: return "trim nurbs surface"; 
        case FbxNodeAttribute::eBoundary: return "boundary"; 
        case FbxNodeAttribute::eNurbsSurface: return "nurbs surface"; 
        case FbxNodeAttribute::eShape: return "shape"; 
        case FbxNodeAttribute::eLODGroup: return "lodgroup"; 
        case FbxNodeAttribute::eSubDiv: return "subdiv"; 
        default: return "unknown";
	}
}

static void printAttribute(FbxNodeAttribute *attribute)
{
	if(!attribute) return;

	FbxString typeName = getAttributeTypeName(attribute->GetAttributeType());
	FbxString attrName = attribute->GetName();
	printTabs();
	printf("<attribute type='%s' name='%s'/>\n", typeName.Buffer(), attrName.Buffer());
}

static void printNode(FbxNode *node)
{
	printTabs();
	const char *nodeName = node->GetName();
	FbxDouble3 translation = node->LclTranslation.Get();
	FbxDouble3 rotation = node->LclRotation.Get();
	FbxDouble3 scaling = node->LclScaling.Get();

	printf("<node name='%s' translation='(%f, %f, %f)' rotation='(%f, %f, %f)' scaling='(%f, %f, %f)'>\n",
		nodeName,
		translation[0], translation[1], translation[2],
		rotation[0], rotation[1], rotation[2],
		scaling[0], scaling[1], scaling[2]
		);
	numTabs++;

	for(int i = 0; i < node->GetNodeAttributeCount(); i++)
		printAttribute(node->GetNodeAttributeByIndex(i));

	for(int j = 0; j < node->GetChildCount(); j++)
		printNode(node->GetChild(j));

	numTabs--;
	printTabs();
	printf("</node>\n");
}

namespace phx
{

FBXLoader::FBXLoader()
{
	
}

void FBXLoader::loadFromFile(const char *filepath, std::vector<phx::Vertex> &vertices)
{
	// Initialize the SDK Manager. This object handles the memory management.
	FbxManager *sdkManager = FbxManager::Create();

	FbxIOSettings *ios = FbxIOSettings::Create(sdkManager, IOSROOT);
	sdkManager->SetIOSettings(ios);

	FbxImporter *importer = FbxImporter::Create(sdkManager, "");
	if(!importer->Initialize(filepath, -1, sdkManager->GetIOSettings()))
	{
		printf("Call to FbxImporter::Initialize() failed.\n");
		printf("Error returned: %s\n\n", importer->GetStatus().GetErrorString());
		exit(-1);
	}

	FbxScene *scene = FbxScene::Create(sdkManager, "myScene");
	importer->Import(scene);
	importer->Destroy();

	// Load data from file to RAM!
	FbxNode *rootNode = scene->GetRootNode();
	recursiveSceneProcessing(rootNode, vertices);
	printf("Vertices: %d\n", vertices.size());

	sdkManager->Destroy();
}

void FBXLoader::recursiveSceneProcessing(FbxNode *node, std::vector<phx::Vertex> &vertices)
{
	if(!node) return;

	for(int i = 0; i < node->GetChildCount(); ++i)
	{
		FbxNode *childNode = node->GetChild(i);
		recursiveSceneProcessing(childNode, vertices);
		if(!childNode->GetNodeAttribute())
			continue;
			
		FbxNodeAttribute::EType attributeType = childNode->GetNodeAttribute()->GetAttributeType();
		if(attributeType != FbxNodeAttribute::eMesh)
			continue;

		FbxMesh *mesh = (FbxMesh*)childNode->GetNodeAttribute();
		FbxVector4 *fbxVertices = mesh->GetControlPoints();
		// Prepare the vector by pre-allocating with the number of vertices,
		// so that push_back is faster and doesn't need to reallocate every push instead
		vertices.reserve(vertices.size() + mesh->GetControlPointsCount() * 3);

		FbxDouble3 translation = node->LclTranslation.Get();
		FbxDouble3 rotation = node->LclRotation.Get();
		FbxDouble3 scaling = node->LclScaling.Get();

		for(int j = 0; j < mesh->GetPolygonCount(); ++j)
		{
			int numVertices = mesh->GetPolygonSize(j);
			assert(numVertices == 3);
				
			for(int k = 0; k < numVertices; ++k)
			{
				int controlPointIndex = mesh->GetPolygonVertex(j, k);

				phx::Vertex vertex;
				// Also add rotation later here..., or make multiple vertices vectors for each mesh node and use the same translation, scaling and rotation for all (dont add to vertex position).
				vertex.position.x = (float)translation.mData[0] + (float)fbxVertices[controlPointIndex].mData[0] * scaling[0];
				vertex.position.y = (float)translation.mData[1] + (float)fbxVertices[controlPointIndex].mData[1] * scaling[1];
				vertex.position.z = (float)translation.mData[2] + (float)fbxVertices[controlPointIndex].mData[2] * scaling[2];
				vertices.push_back(vertex);
			}
		}
	}
}

}